{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "efkE-K8noZoR",
    "outputId": "b6b85164-930e-4eb8-a0d5-3fb9a775c6ba"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<torch._C.Generator at 0x7cb7342e4d90>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torch.nn.utils import clip_grad_norm_\n",
    "from transformers import AutoTokenizer, GPT2Model\n",
    "from datasets import load_dataset\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "\n",
    "torch.manual_seed(12046)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "WNCjOmYLytlT"
   },
   "outputs": [],
   "source": [
    "# Some parameters\n",
    "learning_rate = 6e-4\n",
    "sequence_len = 1024\n",
    "batch_size = 8\n",
    "gra_acc_steps = 8 * 2\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "eval_iters = 64 * 2\n",
    "eval_interval = 50"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "id": "hrZYxt2zytlX"
   },
   "outputs": [],
   "source": [
    "tokenizer = AutoTokenizer.from_pretrained('gpt2')\n",
    "# The model without language modeling head, just embedding model\n",
    "model = GPT2Model.from_pretrained('gpt2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "zSwVKlA-ytlX",
    "outputId": "0bc45625-5707-4d69-b726-101f5e629613"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Dataset({\n",
       "    features: ['question', 'quotes_0', 'answer_0', 'tokens_0', 'score_0', 'quotes_1', 'answer_1', 'tokens_1', 'score_1', 'input_ids_0', 'input_len_0', 'label', 'input_ids_1', 'input_len_1'],\n",
       "    num_rows: 13218\n",
       "})"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def precoss(data):\n",
    "    '''\n",
    "    生成训练文本和标签变量\n",
    "    '''\n",
    "    re = {}\n",
    "    for i in range(2):\n",
    "        key = 'tokens_%s' % i\n",
    "        # 'prefix' and 'completion' already contain the results of tokenization\n",
    "        re['input_ids_%s' % i] = data[key]['prefix'] + data[key]['completion']\n",
    "        # Record the length of text\n",
    "        re['input_len_%s' % i] = len(re['input_ids_%s' % i])\n",
    "        # Define the label according to the score\n",
    "        re['label'] = 0 if data['score_0'] > 0 else 1\n",
    "    return re\n",
    "\n",
    "dataset = load_dataset('openai/webgpt_comparisons', split='train')\n",
    "dataset = dataset.map(precoss)\\\n",
    ".filter(lambda x: x['score_0'] != 0)\\\n",
    ".filter(lambda x: max(x['input_len_0'], x['input_len_1']) < sequence_len)\n",
    "dataset.set_format(type='torch', device=device)\n",
    "dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "PbVi6EJCytlZ",
    "outputId": "2e64db5a-4c9e-4882-a482-e6e1642f5737"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Heterophobia is the irrational fear of what◼[1] Heterophobia (phobia.wikia.org)\n",
      "\n",
      "Heterophobia, also known as sexophobia, is the fear of the opposite sex. The fear is caused by negative experiences with the opposite sex (such as being sexually assaulted), or even genetics and heredity. Heterophobes would avoid encountering people of opposite gender, that is, men should avoid women while women should avoid men. If the person of opposite sex contact the sufferers, symptoms may result, including breathlessness, dizziness, excessive sweating, nausea, dry mouth, feeling sick, shaking, coronary heart palpitations, inability to speak or assume clearly, a fear of dying (thanatophobia), turning into mad or dropping control, a sensation of detachment from actuality or a full blown anxiety attack. \n",
      "\n",
      "There are variety of methods of treating heterophobia◼ Heterophobia is the irrational fear of the opposite sex, coined as Sexophobia [1]. This phobia can be caused by genetics, heredity, negative experiences with the opposite sex, or a combination of these [1].  Symptoms may result from encountering people of the opposite sex, including breathlessness, dizziness, excessive sweating, nausea, dry mouth, feeling sick, shaking, coronary heart palpitations, and anxiety [1].◼\n"
     ]
    }
   ],
   "source": [
    "print(tokenizer.decode(dataset[1]['input_ids_0'], skip_special_tokens=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "id": "qlPk9TBQytla"
   },
   "outputs": [],
   "source": [
    "from torch.nn.utils.rnn import pad_sequence\n",
    "\n",
    "def token_collect(batch):\n",
    "    '''\n",
    "    As the length of text is different, we need do string padding.\n",
    "    '''\n",
    "    re = {}\n",
    "    for i in range(2):\n",
    "        ids = [data['input_ids_%s' % i] for data in batch]\n",
    "        # Use 0 to do string padding\n",
    "        re['input_ids_%s' % i] = pad_sequence(ids, batch_first=True)\n",
    "        re['input_len_%s' % i] = torch.stack([data['input_len_%s' % i] for data in batch])\n",
    "    # Concatenate the label variable\n",
    "    re['label'] = torch.stack([data['label'] for data in batch])\n",
    "    return re"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Y-zLm8aPytla",
    "outputId": "3ba42f14-6ff4-40f1-e04e-6ea9b87edf98"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'input_ids_0': tensor([[ 5195,   466,   314,  ...,     0,     0,     0],\n",
       "         [29054,    12,  9122,  ...,     0,     0,     0],\n",
       "         [ 5195,   857,  2253,  ...,     0,     0,     0],\n",
       "         ...,\n",
       "         [  464,  6675, 17662,  ...,     0,     0,     0],\n",
       "         [ 2437,   466,  9512,  ...,     0,     0,     0],\n",
       "         [ 5195,   857,   352,  ...,    17,    60, 48366]], device='cuda:0'),\n",
       " 'input_len_0': tensor([ 709,  668,  921,  810,  804,  906,  643, 1015], device='cuda:0'),\n",
       " 'input_ids_1': tensor([[ 5195,   466,   314,  ...,     0,     0,     0],\n",
       "         [29054,    12,  9122,  ...,     0,     0,     0],\n",
       "         [ 5195,   857,  2253,  ...,     0,     0,     0],\n",
       "         ...,\n",
       "         [  464,  6675, 17662,  ...,  1382,   510, 48366],\n",
       "         [ 2437,   466,  9512,  ...,     0,     0,     0],\n",
       "         [ 5195,   857,   352,  ...,     0,     0,     0]], device='cuda:0'),\n",
       " 'input_len_1': tensor([791, 651, 724, 691, 949, 983, 873, 275], device='cuda:0'),\n",
       " 'label': tensor([0, 1, 1, 1, 1, 0, 0, 0], device='cuda:0')}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from torch.utils.data import DataLoader, random_split\n",
    "\n",
    "# Split data into train set and test set\n",
    "train_set, test_set = random_split(dataset, [0.8, 0.2])\n",
    "train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, collate_fn=token_collect)\n",
    "test_loader = DataLoader(test_set, batch_size=3, shuffle=True, collate_fn=token_collect)\n",
    "# An example\n",
    "next(iter(train_loader))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NxpTbf2soZoa",
    "outputId": "79a25845-8797-4beb-a11c-a452f5c8338b"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "RewardModel(\n",
       "  (embedding): GPT2Model(\n",
       "    (wte): Embedding(50257, 768)\n",
       "    (wpe): Embedding(1024, 768)\n",
       "    (drop): Dropout(p=0.1, inplace=False)\n",
       "    (h): ModuleList(\n",
       "      (0-11): 12 x GPT2Block(\n",
       "        (ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "        (attn): GPT2Attention(\n",
       "          (c_attn): Conv1D()\n",
       "          (c_proj): Conv1D()\n",
       "          (attn_dropout): Dropout(p=0.1, inplace=False)\n",
       "          (resid_dropout): Dropout(p=0.1, inplace=False)\n",
       "        )\n",
       "        (ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "        (mlp): GPT2MLP(\n",
       "          (c_fc): Conv1D()\n",
       "          (c_proj): Conv1D()\n",
       "          (act): NewGELUActivation()\n",
       "          (dropout): Dropout(p=0.1, inplace=False)\n",
       "        )\n",
       "      )\n",
       "    )\n",
       "    (ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "  )\n",
       "  (score): Linear(in_features=768, out_features=1, bias=False)\n",
       ")"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class RewardModel(nn.Module):\n",
    "\n",
    "    def __init__(self, model):\n",
    "        '''\n",
    "        Reward modeling\n",
    "        \n",
    "        Args:\n",
    "        ----\n",
    "        model: Embedding model\n",
    "        '''\n",
    "        super().__init__()\n",
    "        self.embedding = model\n",
    "        # Score modeling head\n",
    "        self.score = nn.Linear(model.embed_dim, 1, bias=False)\n",
    "\n",
    "    def forward(self, x, seq_len):\n",
    "        '''\n",
    "        Forward pass\n",
    "        \n",
    "        Args:\n",
    "        ----\n",
    "        x: torch.LongTensor, text, shape (B, T)\n",
    "        seq_len: torch.LongTensor, The length of text before padding, shape (B)\n",
    "        返回\n",
    "        ----\n",
    "        score: torch.FloatTensor, scores, shape(B, 1)\n",
    "        '''\n",
    "        B, _ = x.shape\n",
    "        # The embedding of text\n",
    "        emb = self.embedding(x).last_hidden_state  # (B, T, C)\n",
    "        ind = torch.arange(B, device=seq_len.device)\n",
    "        # Get the feature of the last token\n",
    "        pooled_emb = emb[ind, seq_len - 1]         # (B,    C)\n",
    "        score = self.score(pooled_emb)             # (B,    1)\n",
    "        return score\n",
    "\n",
    "r_model = RewardModel(model)\n",
    "# The structure of model\n",
    "r_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "id": "JKnvslXHoZoa"
   },
   "outputs": [],
   "source": [
    "def print_trainable_parameters(model):\n",
    "    \"\"\"\n",
    "    Print the number of trainable parameters\n",
    "    \"\"\"\n",
    "    trainable_params = 0\n",
    "    all_param = 0\n",
    "    for _, param in model.named_parameters():\n",
    "        all_param += param.numel()\n",
    "        if param.requires_grad:\n",
    "            trainable_params += param.numel()\n",
    "    trainable = f'trainable params: {trainable_params:,}'\n",
    "    params = f'all params: {all_param:,}'\n",
    "    percent = f'trainable%: {100 * trainable_params / all_param:.3f}'\n",
    "    print(f'{trainable} || {params} || {percent}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "DgCQBQYAoZob",
    "outputId": "b8fa7e4d-d835-45a6-8c14-b35e7f0ee230"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "trainable params: 38,400 || all params: 124,478,208 || trainable%: 0.031\n"
     ]
    }
   ],
   "source": [
    "from peft import LoraConfig, PeftModel\n",
    "\n",
    "config = LoraConfig(\n",
    "    r=1,\n",
    "    lora_alpha=8,\n",
    "    target_modules=['c_attn'],\n",
    "    lora_dropout=0.4,\n",
    "    # As the shape of c_attn.weight is (fan_in, fan_out), set this parameter to True\n",
    "    # Note: for linear model, the shape of weight is (fan_out, fan_in)\n",
    "    fan_in_fan_out=True,\n",
    "    bias='none',\n",
    "    # The score modeling head also participats fine-tuning\n",
    "    modules_to_save=['score']\n",
    "    )\n",
    "\n",
    "# Add LoRA adapter to model\n",
    "r_model = PeftModel(r_model, config, adapter_name='lora')\n",
    "print_trainable_parameters(r_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8vTRpFG7oZob",
    "outputId": "02a22211-90a8-444a-bbed-5fbbeca41465"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PreferenceModel(\n",
       "  (pref): PeftModel(\n",
       "    (base_model): LoraModel(\n",
       "      (model): RewardModel(\n",
       "        (embedding): GPT2Model(\n",
       "          (wte): Embedding(50257, 768)\n",
       "          (wpe): Embedding(1024, 768)\n",
       "          (drop): Dropout(p=0.1, inplace=False)\n",
       "          (h): ModuleList(\n",
       "            (0-11): 12 x GPT2Block(\n",
       "              (ln_1): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "              (attn): GPT2Attention(\n",
       "                (c_attn): Linear(\n",
       "                  in_features=768, out_features=2304, bias=True\n",
       "                  (lora_dropout): ModuleDict(\n",
       "                    (lora): Dropout(p=0.4, inplace=False)\n",
       "                  )\n",
       "                  (lora_A): ModuleDict(\n",
       "                    (lora): Linear(in_features=768, out_features=1, bias=False)\n",
       "                  )\n",
       "                  (lora_B): ModuleDict(\n",
       "                    (lora): Linear(in_features=1, out_features=2304, bias=False)\n",
       "                  )\n",
       "                  (lora_embedding_A): ParameterDict()\n",
       "                  (lora_embedding_B): ParameterDict()\n",
       "                )\n",
       "                (c_proj): Conv1D()\n",
       "                (attn_dropout): Dropout(p=0.1, inplace=False)\n",
       "                (resid_dropout): Dropout(p=0.1, inplace=False)\n",
       "              )\n",
       "              (ln_2): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "              (mlp): GPT2MLP(\n",
       "                (c_fc): Conv1D()\n",
       "                (c_proj): Conv1D()\n",
       "                (act): NewGELUActivation()\n",
       "                (dropout): Dropout(p=0.1, inplace=False)\n",
       "              )\n",
       "            )\n",
       "          )\n",
       "          (ln_f): LayerNorm((768,), eps=1e-05, elementwise_affine=True)\n",
       "        )\n",
       "        (score): ModulesToSaveWrapper(\n",
       "          (original_module): Linear(in_features=768, out_features=1, bias=False)\n",
       "          (modules_to_save): ModuleDict(\n",
       "            (lora): Linear(in_features=768, out_features=1, bias=False)\n",
       "          )\n",
       "        )\n",
       "      )\n",
       "    )\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class PreferenceModel(nn.Module):\n",
    "\n",
    "    def __init__(self, model):\n",
    "        '''\n",
    "        Build preference model according to the structure of logistic regression\n",
    "        \n",
    "        Args:\n",
    "        ----\n",
    "        model: Reward model\n",
    "        '''\n",
    "        super().__init__()\n",
    "        self.pref = model\n",
    "\n",
    "    def forward(self, data):\n",
    "        '''\n",
    "        Define model loss\n",
    "        \n",
    "        Args:\n",
    "        ----\n",
    "        data: dict, train data\n",
    "        \n",
    "        Returns:\n",
    "        ----\n",
    "        out: torch.FloatTensor, The predictions, shape (B, 2)\n",
    "        loss: torch.FloatTensor, model loss\n",
    "        '''\n",
    "        # The shape of input0 is (B, T), the shape of len0 is (B)\n",
    "        input0, len0 = data['input_ids_0'], data['input_len_0']\n",
    "        input1, len1 = data['input_ids_1'], data['input_len_1']\n",
    "        score0 = self.pref(input0, len0)             # (B, 1)\n",
    "        score1 = self.pref(input1, len1)             # (B, 1)\n",
    "        out = torch.concat((score0, score1), dim=1)  # (B, 2)\n",
    "        loss = F.cross_entropy(out, data['label'])\n",
    "        return out, loss\n",
    "\n",
    "p_model = PreferenceModel(r_model).to(device)\n",
    "# The structure of model\n",
    "p_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "fvccfQxAytlh",
    "outputId": "9dca51ef-d0ae-4498-fd2c-b06b6d14c39c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([[ 0.8231, -2.1441]], device='cuda:0'), tensor(3.0173, device='cuda:0')) tensor([1], device='cuda:0')\n",
      "tensor([[0.8231]], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "# Use an example to show test the model and record the result for latter comparison\n",
    "example = test_set[:1]\n",
    "with torch.no_grad():\n",
    "    p_model.eval()\n",
    "    print(p_model(example), example['label'])\n",
    "    print(r_model(example['input_ids_0'], example['input_len_0']))\n",
    "    p_model.train()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "nyzFiQgwoZod",
    "outputId": "e6a8c52b-0055-49a4-e094-dccb98f5a71d"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'train': 0.9990766048431396, 'test': 1.0543365478515625}"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from contextlib import nullcontext\n",
    "\n",
    "def estimate_loss(model, ctx=nullcontext()):\n",
    "    '''\n",
    "    Estimate the performance of model.\n",
    "    Note: ctx is used for disabling LoRA or mixed precision.\n",
    "    When ctx=nullcontext(), it have no effect.\n",
    "    '''\n",
    "    re = {}\n",
    "    # Put the model on evaluation mode\n",
    "    model.eval()\n",
    "    re['train'] = _loss(model, train_loader, ctx)\n",
    "    re['test'] = _loss(model, test_loader, ctx)\n",
    "    # Put the model on train mode\n",
    "    model.train()\n",
    "    return re\n",
    "\n",
    "@torch.no_grad()\n",
    "def _loss(model, data_loader, ctx):\n",
    "    \"\"\"\n",
    "    Measure the performance of model based on different data sets.\n",
    "    \"\"\"\n",
    "    lossi = []\n",
    "    data_iter= iter(data_loader)\n",
    "    # Use eval_iters batch data to measure the performance\n",
    "    for k in range(eval_iters):\n",
    "        # After one iteration, create another data loader\n",
    "        data = next(data_iter, None)\n",
    "        if data is None:\n",
    "            data_iter = iter(data_loader)\n",
    "            data = next(data_iter, None)\n",
    "        with ctx:\n",
    "            _, loss = model(data)\n",
    "            lossi.append(loss.item())\n",
    "    return torch.tensor(lossi).mean().item()\n",
    "\n",
    "estimate_loss(p_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "id": "cmTDuji-oZod"
   },
   "outputs": [],
   "source": [
    "# The code of get_lr is inspired by https://github.com/karpathy/nanoGPT/blob/master/train.py\n",
    "import math\n",
    "\n",
    "warmup_iters = 100\n",
    "lr_decay_iters = 3000\n",
    "min_lr = learning_rate / 10\n",
    "\n",
    "def get_lr(it):\n",
    "    '''\n",
    "    Adjust learning rate dynamically \n",
    "    it means the step of training\n",
    "    '''\n",
    "    # 1. Linear warmup\n",
    "    if it < warmup_iters:\n",
    "        return learning_rate * it / warmup_iters\n",
    "    # 2. If exceeding lr_decay_iters, return min_lr\n",
    "    if it > lr_decay_iters:\n",
    "        return min_lr\n",
    "    # 3. decay learning rate\n",
    "    decay_ratio = (it - warmup_iters) / (lr_decay_iters - warmup_iters)\n",
    "    assert 0 <= decay_ratio <= 1\n",
    "    coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))\n",
    "    return min_lr + coeff * (learning_rate - min_lr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "id": "wGlqCuDVoZod"
   },
   "outputs": [],
   "source": [
    "# The parameter for gradient clipping\n",
    "grad_clip = 1.0\n",
    "\n",
    "def train_reward_optimum(model, optimizer, data_loader, max_iters=1000):\n",
    "    lossi = []\n",
    "    scaler = torch.cuda.amp.GradScaler(enabled=(device == 'cuda'))\n",
    "    data_iter = iter(data_loader)\n",
    "\n",
    "    for iter_num in range(max_iters):\n",
    "        # Get learning rate\n",
    "        lr = get_lr(iter_num + 1)\n",
    "        for param_group in optimizer.param_groups:\n",
    "            param_group['lr'] = lr\n",
    "        # Gradient accumulation\n",
    "        for i in range(gra_acc_steps):\n",
    "            data = next(data_iter, None)\n",
    "            if data is None:\n",
    "                data_iter = iter(data_loader)\n",
    "                data = next(data_iter, None)\n",
    "            # Mixed precision\n",
    "            # If using a CPU, set dtype to torch.bfloat16\n",
    "            ctx = torch.autocast(device_type=device, dtype=torch.float16)\n",
    "            with ctx:\n",
    "                _, loss = model(data)\n",
    "                lossi.append(loss.item())\n",
    "                loss *= 1 / gra_acc_steps\n",
    "            scaler.scale(loss).backward()\n",
    "        # Gradient clipping\n",
    "        scaler.unscale_(optimizer)\n",
    "        clip_grad_norm_(model.parameters(), grad_clip)\n",
    "        scaler.step(optimizer)\n",
    "        scaler.update()\n",
    "        optimizer.zero_grad(set_to_none=True)\n",
    "\n",
    "        if iter_num % eval_interval == 0:\n",
    "            # Measure the performance (use mixed precision)\n",
    "            stats = estimate_loss(model, ctx)\n",
    "            train_loss = f'train loss {stats[\"train\"]:.4f}'\n",
    "            eval_loss = f'test loss {stats[\"test\"]:.4f}'\n",
    "            print(f'step {iter_num:>4}: {train_loss}, {eval_loss}')\n",
    "    return lossi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "I_Gu83XLoZoe",
    "outputId": "124df17c-560d-4192-df8e-cef906d66ec5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "step    0: train loss 1.0442, test loss 0.9501\n",
      "step   50: train loss 0.6830, test loss 0.6951\n",
      "step  100: train loss 0.6545, test loss 0.6703\n",
      "step  150: train loss 0.6529, test loss 0.6690\n",
      "step  200: train loss 0.6491, test loss 0.6514\n",
      "step  250: train loss 0.6295, test loss 0.6609\n",
      "step  300: train loss 0.6293, test loss 0.6686\n",
      "step  350: train loss 0.6468, test loss 0.6415\n",
      "step  400: train loss 0.6317, test loss 0.6807\n",
      "step  450: train loss 0.6258, test loss 0.6348\n",
      "step  500: train loss 0.6277, test loss 0.6426\n",
      "step  550: train loss 0.6260, test loss 0.6535\n",
      "step  600: train loss 0.6235, test loss 0.6457\n",
      "step  650: train loss 0.6209, test loss 0.6771\n",
      "step  700: train loss 0.6109, test loss 0.6656\n",
      "step  750: train loss 0.6087, test loss 0.6395\n",
      "step  800: train loss 0.6060, test loss 0.6544\n",
      "step  850: train loss 0.5922, test loss 0.6524\n",
      "step  900: train loss 0.6052, test loss 0.6679\n",
      "step  950: train loss 0.5957, test loss 0.6456\n"
     ]
    }
   ],
   "source": [
    "# Parameters for AdamW\n",
    "weight_decay = 1e-1\n",
    "beta1 = 0.9\n",
    "beta2 = 0.95\n",
    "optimizer = optim.AdamW(p_model.parameters(), lr=learning_rate,\n",
    "                        betas=(beta1, beta2), weight_decay=weight_decay)\n",
    "l = train_reward_optimum(p_model, optimizer, train_loader, max_iters=1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 448
    },
    "id": "le_tOTdloZoe",
    "outputId": "de1cdc16-1e76-4991-c70e-47e87d44066c"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7cb5d0a598d0>]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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LoIuuoKRkpISZGdnIy2tajZDs9mMtLQ0ZGZmKq53+fJlNGvWDElJSRg2bBh27drlfomJiIhIlclkwl3dmxhdDM10BSNnz55FeXm5U81GQkICcnNzZddp06YN5s+fj++++w6fffYZrFYrevfujePHjyvup7i4GAUFBZIfIiIi0s7AqWZ08/lomtTUVIwePRpdunRBv379sHjxYtSvXx/vvvuu4jrp6emIi4uz/yQlJfm6mERERGQQXcFIfHw8LBYL8vLyJK/n5eUhMTFR0zbCw8PRtWtXHDx4UHGZqVOnIj8/3/5z7NgxPcUkIiIKeUFUMaIvGImIiEC3bt2QkZFhf81qtSIjIwOpqamatlFeXo4dO3agYcOGistERkYiNjZW8kNERETaBVMzje5ZeydPnowxY8age/fu6NmzJ2bPno3CwkKMHTsWADB69Gg0btwY6enpAIAXXngB1113HVq2bImLFy/i1VdfxdGjR/HAAw9490iIiIjITgiiuhHdwciIESNw5swZTJ8+Hbm5uejSpQuWLVtm79Sak5MDs7mqwuXChQsYN24ccnNzUadOHXTr1g2///472rdv772jICIioqBlEoTAr8gpKChAXFwc8vPz2WRDRESkweSFW7F4ywkAwJGZQwwpg9b7N+emISIiqoYCvqZBhMEIERERGYrBCBERUTUUBL0w7BiMEBERkaEYjBAREVVDwVMvwmCEiIioWgqiVhoGI0RERNVREMUiDEaIiIiqI3ZgJSIiItKIwQgREVE1FDz1IgxGiIiIqqXuzeoYXQTNdE+UR0RERIFv1HXNEBlmQa8WdY0uiksMRoiIiKqhMIsZI3s1NboYmrCZhoiIiAzFYISIiIgMxWCEiIiIDMVghIiIiAzFYISIiIgMxWCEiIiIDMVghIiIiAzFYISIiIgMxWCEiIiIDMVghIiIiAzFYISIiIgMxWCEiIiIDMVghIiIiAwVFLP2CoIAACgoKDC4JERERKSV7b5tu48rCYpg5NKlSwCApKQkg0tCREREel26dAlxcXGK75sEV+FKALBarTh58iRq1aoFk8nkte0WFBQgKSkJx44dQ2xsrNe2G0iq+zFW9+MDqv8x8viCX3U/xup+fIDvjlEQBFy6dAmNGjWC2azcMyQoakbMZjOaNGnis+3HxsZW2xPMprofY3U/PqD6HyOPL/hV92Os7scH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\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(torch.tensor(l).view(-1, 10).mean(1).numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "9FxIeHbBoZof",
    "outputId": "cc0d168b-95ee-4e7e-c3be-054d66919b74"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([[2.9113, 2.4072]], device='cuda:0'), tensor(0.9766, device='cuda:0')) tensor([1], device='cuda:0')\n",
      "tensor([[2.9113]], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "# After fine-tuning, the performance of reward model has been improved\n",
    "with torch.no_grad():\n",
    "    p_model.eval()\n",
    "    print(p_model(example), example['label'])\n",
    "    print(r_model(example['input_ids_0'], example['input_len_0']))\n",
    "    p_model.train()"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "A100",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
